#include "flutter_qt_7ze.h"

#include <filesystem>
#include <fstream>
#include <iostream>
#include <locale>

#ifdef __ANDROID__
    #include <jni.h>
    #include <android/asset_manager_jni.h>
    #include "android/7zAssetFile.h"
#endif

#include "7zVersion.h"
#include "7zFile.h"
#include "7z.h"
#include "7zAlloc.h"
#include "7zBuf.h"
#include "7zCrc.h"
#include "7zVersion.h"

//==============================================

std::wstring ushortArrayToWstring(const unsigned short *array, size_t length)
{
    std::wstring result;
    // 注意：这里假设wchar_t的大小为2字节，与unsigned short相同
    for (size_t i = 0; i < length; ++i)
    {
        if (array[i] == '\0')
        {
            break;
        }
        result.push_back(static_cast<wchar_t>(array[i]));
    }
    return result;
}

std::wstring constCharToWString(const char *cstr)
{
    if (cstr == nullptr)
    {
        return std::wstring();
    }

    size_t size = std::mbstowcs(nullptr, cstr, 0);
    if (size == static_cast<size_t>(-1))
    {
        // Error handling, possibly throw an exception
        return std::wstring();
    }

    std::wstring wstr(size, L'\0');
    size = std::mbstowcs(&wstr[0], cstr, size + 1);
    if (size == static_cast<size_t>(-1))
    {
        // Error handling, possibly throw an exception
        return std::wstring();
    }
    return wstr;
}

namespace fs = std::filesystem;
int MyCreateDir(std::wstring wstrPath)
{
    fs::path dirPath = wstrPath;
    // 检查目录是否已存在
    if (fs::exists(dirPath) && fs::is_directory(dirPath))
    {
        return 0;
    }
    else
    {
        // 创建目录
        try
        {
            if (fs::create_directories(dirPath))
            {
                return 0;
            }
            else
            {
                return -1;
            }
        }
        catch (fs::filesystem_error &e)
        {
            //            std::cerr << e.what() << std::endl;
        }
    }

    return -1;
}

#define kInputBufSize ((size_t)1 << 18)
static const ISzAlloc g_Alloc = {SzAlloc, SzFree};

int archiveByStream(ISeekInStreamPtr seekStream, const char *outPath){
    ISzAlloc allocImp = g_Alloc;
    ISzAlloc allocTempImp = g_Alloc;

   // CFileInStream archiveStream;
    CLookToRead2 lookStream;
    CSzArEx db;
    SRes res;
    UInt16 *temp = NULL;
    size_t tempSize = 0;
//    {
//        WRes wres = InFile_Open(&archiveStream.file, fileName);
//        if (wres != 0)
//        {
//            return 1;
//        }
//    }
//
//    FileInStream_CreateVTable(&archiveStream);
//    archiveStream.wres = 0;
    LookToRead2_CreateVTable(&lookStream, False);
    lookStream.buf = NULL;

    res = SZ_OK;
    {
        lookStream.buf = (Byte *)ISzAlloc_Alloc(&allocImp, kInputBufSize);
        if (!lookStream.buf)
            res = SZ_ERROR_MEM;
        else
        {
            lookStream.bufSize = kInputBufSize;
            lookStream.realStream = seekStream;//&archiveStream.vt;
            LookToRead2_INIT(&lookStream)
        }
    }

    CrcGenerateTable();
    SzArEx_Init(&db);

    if (res == SZ_OK)
    {
        res = SzArEx_Open(&db, &lookStream.vt, &allocImp, &allocTempImp);
    }

    UInt32 blockIndex = 0xFFFFFFFF;
    Byte *outBuffer = 0;
    size_t outBufferSize = 0;

    for (int i = 0; i < db.NumFiles; i++)
    {
        size_t offset = 0;
        size_t outSizeProcessed = 0;
        const BoolInt isDir = SzArEx_IsDir(&db, i);

        size_t len = SzArEx_GetFileNameUtf16(&db, i, NULL);

        if (len > tempSize)
        {
            SzFree(NULL, temp);
            tempSize = len;
            temp = (UInt16 *)SzAlloc(NULL, tempSize * sizeof(temp[0]));
            if (!temp)
            {
                res = SZ_ERROR_MEM;
                break;
            }
        }
        if (res != 0)
        {
            return res;
        }

        SzArEx_GetFileNameUtf16(&db, i, temp);

        std::wstring destPath = constCharToWString(outPath) + constCharToWString("/") + ushortArrayToWstring(temp, len);
        if (isDir)
        {
            MyCreateDir(destPath);
            continue;
        }

        {
            res = SzArEx_Extract(&db, &lookStream.vt, i,
                                 &blockIndex, &outBuffer, &outBufferSize,
                                 &offset, &outSizeProcessed,
                                 &allocImp, &allocTempImp);
            if (res != SZ_OK)
            {
                return res;
            }

            std::ofstream binFile(destPath, std::ios::binary | std::ios::out);
            if (binFile.is_open())
            {
                binFile.write(reinterpret_cast<char *>(outBuffer + offset), outSizeProcessed);
                binFile.close();
            }
        }
    }
    ISzAlloc_Free(&allocImp, outBuffer);

    SzFree(NULL, temp);
    SzArEx_Free(&db, &allocImp);
    ISzAlloc_Free(&allocImp, lookStream.buf);

   // File_Close(&archiveStream.file);
    return res;
}


#ifdef __cplusplus
extern "C"
{
#endif

int extractFile(const char *fileName, const char *outPath)
{
    CFileInStream archiveStream;
    WRes wres = InFile_Open(&archiveStream.file, fileName);
    if (wres != 0)
    {
            return 1;
        }

    FileInStream_CreateVTable(&archiveStream);
    archiveStream.wres = 0;
    auto ret = archiveByStream(&archiveStream.vt, outPath);
    File_Close(&archiveStream.file);
    return ret;
}

char versionBuff[10] = {0x00};
const char* getLzmaVersion(){
    if(strlen(versionBuff) == 0){
         strcpy(versionBuff, MY_VERSION_NUMBERS);
    }
   
    return versionBuff;
}

//==============================================
#ifdef __ANDROID__
    JNIEXPORT jboolean JNICALL Java_com_seantone_utils_LZMASDK_extractFile(JNIEnv *env, jobject self, jstring fileName, jstring outPath)
    {
        const char *fileNameStr = (*env).GetStringUTFChars(fileName, 0);
        const char *outPathStr = (*env).GetStringUTFChars(outPath, 0);
        auto ret = extractFile(fileNameStr, outPathStr);
        (*env).ReleaseStringUTFChars(fileName, fileNameStr);
        (*env).ReleaseStringUTFChars(outPath, outPathStr);
        return ret == 0;
    }

    JNIEXPORT jboolean JNICALL Java_com_seantone_utils_LZMASDK_extractAsset(JNIEnv *env, jobject self, jobject assetsManager, jstring fileName, jstring outPath)
    {
        AAssetManager *mgr = AAssetManager_fromJava(env, assetsManager);
        const char *fileNameStr = (*env).GetStringUTFChars(fileName, 0);
        const char *outPathStr = (*env).GetStringUTFChars(outPath, 0);

        CAssetFileInStream archiveStream;
        if (InAssetFile_Open(mgr, &archiveStream.assetFile, fileNameStr)) {
            //return SZ_ERROR_ARCHIVE;

            (*env).ReleaseStringUTFChars(fileName, fileNameStr);
            (*env).ReleaseStringUTFChars(outPath, outPathStr);

            return false;
        }

        AssetFileInStream_CreateVTable(&archiveStream);
        SRes res = archiveByStream( &archiveStream.vt,  outPathStr );
        AssetFile_Close(&archiveStream.assetFile);


        (*env).ReleaseStringUTFChars(fileName, fileNameStr);
        (*env).ReleaseStringUTFChars(outPath, outPathStr);

        return true;
    }

    JNIEXPORT jstring JNICALL Java_com_seantone_utils_LZMASDK_getLzmaVersion(JNIEnv *env, jobject self)
    {
        return (*env).NewStringUTF(MY_VERSION_NUMBERS);
    }

#endif

#ifdef __cplusplus
}
#endif